//------------------------------------------------------------------------------
//
// Service A - Sample service that partners with Service B
//
// Written Jan-2008 by Trevor Taylor, Software Technology, Australia
//
// Updated to MRDS 2.0 Dec-2008
//
//------------------------------------------------------------------------------
//
// NOTES:
// You must build ServiceB before ServiceA. The Dependencies in the
// Solution file are set up appropriately.
//
// You can select various different versions of the code based on
// defining ONE (and only one) of the following preprocessor directives.
//  If you comment them all out, nothing happens!
//#define CREATE_SERVICEB
#define FIND_SERVICEB
//#define CARRY_ON

using Microsoft.Ccr.Core;
using Microsoft.Dss.Core;
using Microsoft.Dss.Core.Attributes;
using Microsoft.Dss.ServiceModel.Dssp;
using Microsoft.Dss.ServiceModel.DsspServiceBase;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Xml;
using W3C.Soap;
using servicea = Robotics.ServiceA;

using serviceb = Robotics.ServiceB.Proxy;


namespace Robotics.ServiceA
{
    /// <summary>
    /// Implementation class for ServiceA
    /// </summary>
    [DisplayName("ServiceA")]
    [Description("The ServiceA Service")]
    [Contract(Contract.Identifier)]
    public class ServiceAService : DsspServiceBase
    {
        
        /// <summary>
        /// _state
        /// </summary>
        private ServiceAState _state = new ServiceAState();
        
        /// <summary>
        /// _main Port
        /// </summary>
        [ServicePort("/servicea", AllowMultipleInstances=false)]
        private ServiceAOperations _mainPort = new ServiceAOperations();

#if !(FIND_SERVICEB || CARRY_ON)
        // Create a port to access Service B,
        // but we don't know where to send messages yet
        serviceb.ServiceBOperations _servicebPort = null;
#endif

#if FIND_SERVICEB || CARRY_ON
        // Partner with ServiceB
        [Partner("ServiceB", Contract = serviceb.Contract.Identifier,
            CreationPolicy = PartnerCreationPolicy.UsePartnerListEntry, Optional = false)]
        private serviceb.ServiceBOperations _servicebPort = new serviceb.ServiceBOperations();
#endif

        /// <summary>
        /// Default Service Constructor
        /// </summary>
        public ServiceAService(DsspServiceCreationPort creationPort) : 
                base(creationPort)
        {
        }
        
        /// <summary>
        /// Service Start
        /// </summary>
        protected override void Start()
        {
			base.Start();
			// Add service specific initialization here.

            // NOTE: You should NOT get in the habit of using Console.WriteLine.
            // Use LogInfo(LogGroups.Console, "...") or LogError() instead.
            // However, for clarity in the output we have used Console.WriteLine
            // extensively in this example.
            Console.WriteLine("ServiceA starting");

            // Some useful information:
            //Console.WriteLine(".NET Version: " + System.Environment.Version);
            //Console.WriteLine(LayoutPaths.LocalServicePath);
            //Console.WriteLine(LayoutPaths.RootDir);

#if CREATE_SERVICEB || FIND_SERVICEB || CARRY_ON
            // Start the main task on a separate thread and return
            SpawnIterator(MainTask);
#endif
        }

        private IEnumerator<ITask> MainTask()
        {
            Port<EmptyValue> done = new Port<EmptyValue>();

#if CREATE_SERVICEB
            SpawnIterator<Port<EmptyValue>>(done, CreatePartner);
#endif
#if FIND_SERVICEB
            SpawnIterator<Port<EmptyValue>>(done, FindPartner);
#endif
#if !CARRY_ON
            // Wait for a message to say that ServiceB is up and running
            yield return Arbiter.Receive(
                false,
                done,
                EmptyHandler
            );
#endif

            // Check that we have a forwarder
            if (_servicebPort == null)
            {
                LogError("There is no ServiceB");
                yield break;
            }
            else
            {
                SpawnIterator<Port<EmptyValue>>(done, MainLoop);
                yield return Arbiter.Receive(
                    false,
                    done,
                    EmptyHandler
                );
            }

            // We no longer require ServiceB so shut it down
            // NOTE: This is not necessary -- it is just here to illustrate that
            // other services can be shut down
            LogInfo(LogGroups.Console, "Dropping ServiceB ...");
            _servicebPort.DsspDefaultDrop();

            Console.WriteLine("ServiceA finished");
            // Wait a while for ServiceB to exit
            yield return Arbiter.Receive(
                false,
                TimeoutPort(500),
                delegate(DateTime time)
                { }
            );

            // Pause for user input --
            // This is just so that the DSS node stays up for the time being
            Console.WriteLine("Press Enter to exit:");
            Console.ReadLine();
            // Shut down the DSS node
            ControlPanelPort.Post(new Microsoft.Dss.Services.ControlPanel.DropProcess());

            yield break;
        }

#if CREATE_SERVICEB

        private IEnumerator<ITask> CreatePartner(Port<EmptyValue> p)
        {
            // Create ServiceB instance
            Console.WriteLine("Creating new ServiceB");
            yield return Arbiter.Choice(CreateService(serviceb.Contract.Identifier),
                delegate(CreateResponse s)
                {
                    // Create Request succeeded.
                    LogInfo(LogGroups.Console, "ServiceB created: " + s.Service);

                    Uri addr;
                    try
                    {
                        // Create URI from service instance string
                        addr = new Uri(s.Service);

                        // Create forwarder to ServiceB
                        _servicebPort = ServiceForwarder<serviceb.ServiceBOperations>(addr);
                    }
                    catch (Exception ex)
                    {
                        LogError(LogGroups.Console, "Could not create forwarder: " + ex.Message);
                    }
                },
                delegate(W3C.Soap.Fault failure)
                {
                    // Request failed
                    LogError(LogGroups.Console, "Could not start ServiceB");
                }
            );

            // Signal that the create is finished
            p.Post(EmptyValue.SharedInstance);

            yield break;
        }
#endif

#if FIND_SERVICEB

        private IEnumerator<ITask> FindPartner(Port<EmptyValue> p)
        {
            // Find ServiceB in the local directory
            Console.WriteLine("Finding ServiceB in the Directory");
            yield return Arbiter.Choice(
                DirectoryQuery(serviceb.Contract.Identifier, DsspOperation.DefaultLongTimeSpan),
                delegate(ServiceInfoType s)
                {
                    // Request succeeded
                    LogInfo(LogGroups.Console, "Found ServiceB: " + s.Service);

                    Uri addr;
                    try
                    {
                        // Create URI from service instance string
                        addr = new Uri(s.Service);

                        // Now create service forwarder to ServiceB
                        _servicebPort = ServiceForwarder<serviceb.ServiceBOperations>(addr);
                    }
                    catch (Exception ex)
                    {
                        LogError(LogGroups.Console, "Could not create forwarder: " + ex.Message);
                        _servicebPort = null;
                    }
                },
                delegate(W3C.Soap.Fault failure)
                {
                    // Request failed
                    LogError(LogGroups.Console, "Could not find ServiceB");
                    _servicebPort = null;
                }
            );

            // Signal that the find is finished
            p.Post(EmptyValue.SharedInstance);

            yield break;
        }

#endif

        private IEnumerator<ITask> MainLoop(Port<EmptyValue> p)
        {
            // Issue several Gets to ServiceB
            yield return Arbiter.ExecuteToCompletion(Environment.TaskQueue,
                Arbiter.FromIteratorHandler(GetData));

            // Change the update interval on ServiceB
            _servicebPort.SetInterval(1500);

            // Do some more Gets to see the effect of the changed interval
            yield return Arbiter.ExecuteToCompletion(Environment.TaskQueue,
                Arbiter.FromIteratorHandler(GetData));

            // Finall, post a message to say that we are finished
            p.Post(EmptyValue.SharedInstance);

            yield break;

        }


        // Request "sensor" data from ServiceB
        private IEnumerator<ITask> GetData()
        {
            for (int i = 0; i < 10; i++)
            {
                // Send a Get request to ServiceB
                yield return Arbiter.Choice(_servicebPort.Get(),
                    delegate(serviceb.ServiceBState s)
                    {
                        // Get request succeeded
                        LogInfo(LogGroups.Console, "ServiceB Sensor: " + s.SensorValue);
                    },
                    delegate(W3C.Soap.Fault failure)
                    {
                        // Get request failed
                        LogError(LogGroups.Console, "Get to ServiceB failed" + failure.Reason);
                    }
                );

                // Wait for 1 second
                yield return Arbiter.Receive(
                    false,
                    TimeoutPort(1000),
                    delegate(DateTime time)
                    { }
                );

            }

            yield break;
        }

        /// <summary>
        /// Get Handler
        /// </summary>
        /// <param name="get"></param>
        /// <returns></returns>
        [ServiceHandler(ServiceHandlerBehavior.Concurrent)]
        public virtual IEnumerator<ITask> GetHandler(Get get)
        {
            get.ResponsePort.Post(_state);
            yield break;
        }
    }
}
